﻿// <copyright file="ABTesting.cs" company="Microsoft Corporation">
// Copyright (c) 2009 Microsoft Corporation All Rights Reserved
// </copyright>
// <author>Michael S. Scherotter</author>
// <email>mischero@microsoft.com</email>
// <date>2009-10-08</date>
// <summary>ABTesting Behavior for web analytics</summary>

namespace Microsoft.WebAnalytics.Behaviors
{
    using System;
    using System.ComponentModel.Composition;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Interactivity;

    /// <summary>
    /// <img src="../media/Microsoft.WebAnalytics.Behaviors.ABTesting.24x24.png"/> 
    /// A/B Testing behavior
    /// </summary>
    /// <remarks>Add this behavior to use the Visual State Manager to enable 
    /// <a href="http://en.wikipedia.org/wiki/A/B_Testing">A/B Testing</a>.
    /// You can use the Application initialization Parameters to force the selection of 
    /// a variant.
    /// <example>
    /// Setting the Variant to "A"
    /// <code>
    /// &lt;object ...&gt;
    ///     &lt;param name="initparams" value="SAFVariant=A"/&gt;
    /// &lt;/object&gt;
    /// </code>
    /// </example>
    /// <example>
    /// Setting the Variant to control group (removing the persistent variant - "control" is a special value here).
    /// <code>
    /// &lt;object ...&gt;
    ///     &lt;param name="initparams" value="SAFVariant=control"/&gt;
    /// &lt;/object&gt;
    /// </code>
    /// </example>
    /// </remarks>
    public class ABTesting : Behavior<FrameworkElement>
    {
        #region Fields
        #endregion

        #region Properties
        /// <summary>
        /// Gets or sets the A/B Testing state name
        /// </summary>
        /// <remarks>Imported via MEF</remarks>
        [Import]
        public string ABTestingStateName { get; set; }

        /// <summary>
        /// Gets or sets the function to save the testing state to isolated storage
        /// </summary>
        [Import]
        public Action<string> SaveTestingState { get; set; }

        /// <summary>
        /// Gets or sets the state group to use for A/B Testing
        /// </summary>
        public string StateGroup { get; set; }

        /// <summary>
        /// Gets or sets the default state to use when A/B testing is not applied
        /// </summary>
        public string DefaultState { get; set; }

        /// <summary>
        /// Gets or sets the percent of traffic to try an alternative state design
        /// </summary>
        public double PercentageOfTraffic { get; set; }

        #endregion

        #region Methods
        /// <summary>
        /// Attach the A/B testing handler
        /// </summary>
        protected override void OnAttached()
        {
            base.OnAttached();

            CompositionInitializerEx.SatisfyImports(this);

            this.AssociatedObject.Loaded += new RoutedEventHandler(this.AssociatedObject_Loaded);
        }

        #endregion

        #region Implementation

        /// <summary>
        /// Find the root of the scene
        /// </summary>
        /// <param name="control">a control in the scene</param>
        /// <returns>the root control that has no parent</returns>
        private static Control FindRoot(FrameworkElement control)
        {
            if (control == null)
            {
                return null;
            }

            if (control.Parent == null)
            {
                return control as Control;
            }

            return FindRoot(control.Parent as FrameworkElement);
        }

        /// <summary>
        /// Update the state
        /// </summary>
        /// <param name="sender">the AssociatedObject</param>
        /// <param name="e">the routed event arguments</param>
        private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
        {
            this.UpdateState();
        }

        /// <summary>
        /// Update the state for A/B testing
        /// </summary>
        private void UpdateState()
        {
            var control = FindRoot(this.AssociatedObject);

            ////var control = Application.Current.RootVisual as Control;

            if (control == null)
            {
                return;
            }

            if (!string.IsNullOrEmpty(this.ABTestingStateName))
            {
                this.SelectState(control);

                return;
            }

            var random = new Random(System.DateTime.Now.Millisecond);

            if (random.NextDouble() > this.PercentageOfTraffic)
            {
                this.SaveTestingState(this.DefaultState);
                ////this.ABTestingStateName = this.DefaultState;

                this.SelectState(control);

                return;
            }

            if (string.IsNullOrEmpty(this.StateGroup))
            {
                return;
            }

            var groups = VisualStateManager.GetVisualStateGroups(this.AssociatedObject);

            var selectedGroup = (from stateGroup in groups.Cast<VisualStateGroup>()
                                 where stateGroup.Name == this.StateGroup
                                 select stateGroup).FirstOrDefault();

            if (selectedGroup == null)
            {
                return;
            }

            int randomStateIndex = random.Next(selectedGroup.States.Count);

            var randomState = selectedGroup.States[randomStateIndex] as VisualState;

            this.SaveTestingState(randomState.Name);
            ////this.ABTestingStateName = randomState.Name;

            this.SelectState(control);
        }

        /// <summary>
        /// Select a state name
        /// </summary>
        /// <param name="control">the control which contains the Visual State Manager</param>
        private void SelectState(Control control)
        {
            if (!string.IsNullOrEmpty(this.ABTestingStateName))
            {
                VisualStateManager.GoToState(control, this.ABTestingStateName, false);
            }
        }

        #endregion
    }
}
